using System;
using System.IO;
using System.Runtime.CompilerServices;
using System.Diagnostics;
using System.Collections.Generic;
using org.neo4j.graphdb;

//
// * Copyright (c) 2002-2010 "Neo Technology,"
// *     Network Engine for Objects in Lund AB [http://neotechnology.com]
// *
// * This file is part of Neo4j.
// * 
// * Neo4j is free software: you can redistribute it and/or modify
// * it under the terms of the GNU Affero General Public License as
// * published by the Free Software Foundation, either version 3 of the
// * License, or (at your option) any later version.
// * 
// * This program is distributed in the hope that it will be useful,
// * but WITHOUT ANY WARRANTY; without even the implied warranty of
// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// * GNU Affero General Public License for more details.
// * 
// * You should have received a copy of the GNU Affero General Public License
// * along with this program. If not, see <http://www.gnu.org/licenses/>.
// 
namespace org.neo4j.kernel
{


	using TransactionManager = javax.transaction.TransactionManager;

	using RelationshipType = org.neo4j.graphdb.RelationshipType;
	using LockReleaser = org.neo4j.kernel.impl.core.LockReleaser;
	using NioNeoDbPersistenceSource = org.neo4j.kernel.impl.nioneo.xa.NioNeoDbPersistenceSource;
	using LockManager = org.neo4j.kernel.impl.transaction.LockManager;
	using TransactionFailureException = org.neo4j.kernel.impl.transaction.TransactionFailureException;
	using TxModule = org.neo4j.kernel.impl.transaction.TxModule;
	using XaDataSource = org.neo4j.kernel.impl.transaction.xaframework.XaDataSource;
	using FileUtils = org.neo4j.kernel.impl.util.FileUtils;

	internal class GraphDbInstance
	{
		private const string NIO_NEO_DB_CLASS = "org.neo4j.kernel.impl.nioneo.xa.NeoStoreXaDataSource";
		private const string DEFAULT_DATA_SOURCE_NAME = "nioneodb";

		private const string LUCENE_DS_CLASS = "org.neo4j.index.lucene.LuceneDataSource";
		private const string LUCENE_FULLTEXT_DS_CLASS = "org.neo4j.index.lucene.LuceneFulltextDataSource";

		private bool started = false;
		private bool create;
		private string storeDir;

		internal GraphDbInstance(string storeDir, bool create)
		{
			this.storeDir = storeDir;
			this.create = create;
		}

		private Config config = null;

		private NioNeoDbPersistenceSource persistenceSource = null;

		public virtual Config getConfig()
		{
			return config;
		}

		public virtual void Start()
		{
			Start(new Dictionary<string, string>());
		}

		private Dictionary<object, object> getDefaultParams()
		{
			Dictionary<object, object> @params = new Dictionary<object, object>();
			@params.Add("neostore.nodestore.db.mapped_memory", "20M");
			@params.Add("neostore.propertystore.db.mapped_memory", "90M");
			@params.Add("neostore.propertystore.db.index.mapped_memory", "1M");
			@params.Add("neostore.propertystore.db.index.keys.mapped_memory", "1M");
			@params.Add("neostore.propertystore.db.strings.mapped_memory", "130M");
			@params.Add("neostore.propertystore.db.arrays.mapped_memory", "130M");
			@params.Add("neostore.relationshipstore.db.mapped_memory", "100M");
		// if on windows, default no memory mapping
			string nameOs = System.getProperty("os.name");
			if (nameOs.StartsWith("Windows"))
			{
				@params.Add("use_memory_mapped_buffers", "false");
			}
			return @params;
		}

///    
///     <summary> * Starts Neo4j with default configuration
///     *  </summary>
///     * <param name="storeDir"> path to directory where Neo4j store is located </param>
///     * <param name="create"> if true a new Neo4j store will be created if no store exist
///     *            at <CODE>storeDir</CODE> </param>
///     * <param name="configuration"> parameters </param>
///     * <exception cref="StartupFailedException"> if unable to start </exception>
///     
		[MethodImpl(MethodImplOptions.Synchronized)]
		public virtual void Start(Dictionary<string, string> stringParams)
		{
			if (started)
			{
				throw new IllegalStateException("Neo4j instance already started");
			}
			Dictionary<object, object> @params = getDefaultParams();
			foreach (KeyValuePair<string, string> entry in stringParams.entrySet())
			{
				@params.Add(entry.Key, entry.Value);
			}
			config = new Config(storeDir, @params);
			storeDir = FileUtils.fixSeparatorsInPath(storeDir);
			string separator = System.getProperty("file.separator");
			string store = storeDir + separator + "neostore";
			@params.Add("store_dir", storeDir);
			@params.Add("neo_store", store);
			@params.Add("create", Convert.ToString(create));
			string logicalLog = storeDir + separator + "nioneo_logical.log";
			@params.Add("logical_log", logicalLog);
			sbyte[] resourceId = "414141".getBytes();
			@params.Add(typeof(LockManager), config.getLockManager());
			@params.Add(typeof(LockReleaser), config.getLockReleaser());
			config.getTxModule().registerDataSource(DEFAULT_DATA_SOURCE_NAME, NIO_NEO_DB_CLASS, resourceId, @params);
		// hack for lucene index recovery if in path
			XaDataSource lucene = null;
			XaDataSource luceneFulltext = null;
			if (!config.isReadOnly())
			{
				try
				{
					System.Type clazz = System.Type.GetType(LUCENE_DS_CLASS);
					cleanWriteLocksInLuceneDirectory(storeDir + "/lucene");
					sbyte[] luceneId = "162373".getBytes();
					lucene = registerLuceneDataSource("lucene", clazz.Name, config.getTxModule(), storeDir + "/lucene", config.getLockManager(), luceneId);
					clazz = System.Type.GetType(LUCENE_FULLTEXT_DS_CLASS);
					cleanWriteLocksInLuceneDirectory(storeDir + "/lucene-fulltext");
					luceneId = "262374".getBytes();
					luceneFulltext = registerLuceneDataSource("lucene-fulltext", clazz.Name, config.getTxModule(), storeDir + "/lucene-fulltext", config.getLockManager(), luceneId);
				}
				catch (ClassNotFoundException e)
				{ // ok index util not on class path
				}
			}
		// System.setProperty( "neo.tx_log_directory", storeDir );
			persistenceSource = new NioNeoDbPersistenceSource();
			config.setNeoPersistenceSource(DEFAULT_DATA_SOURCE_NAME, create);
			config.getIdGeneratorModule().setPersistenceSourceInstance(persistenceSource);
			config.getEventModule().init();
			config.getTxModule().init();
			config.getPersistenceModule().init();
			persistenceSource.init();
			config.getIdGeneratorModule().init();
			config.getNeoModule().init();

			config.getEventModule().Start();
			config.getTxModule().Start();
			config.getPersistenceModule().Start(config.getTxModule().getTxManager(), persistenceSource);
			persistenceSource.Start(config.getTxModule().getXaDataSourceManager());
			config.getIdGeneratorModule().Start();
			config.getNeoModule().Start(config.getLockReleaser(), config.getPersistenceModule().getPersistenceManager(), @params);
			if (lucene != null)
			{
				config.getTxModule().getXaDataSourceManager().unregisterDataSource("lucene");
				lucene = null;
			}
			if (luceneFulltext != null)
			{
				config.getTxModule().getXaDataSourceManager().unregisterDataSource("lucene-fulltext");
				luceneFulltext = null;
			}
			started = true;
		}

		private void cleanWriteLocksInLuceneDirectory(string luceneDir)
		{
			File dir = new File(luceneDir);
			if (!dir.isDirectory())
			{
				return;
			}
			foreach (File file in dir.listFiles())
			{
				if (file.isDirectory())
				{
					cleanWriteLocksInLuceneDirectory(file.getAbsolutePath());
				}
				else if (file.Name.Equals("write.lock"))
				{
					bool success = file.delete();
					Debug.Assert(success);
				}
			}
		}

		private XaDataSource registerLuceneDataSource(string name, string className, TxModule txModule, string luceneDirectory, LockManager lockManager, sbyte[] resourceId)
		{
			Dictionary<object, object> @params = new Dictionary<object, object>();
			@params.Add("dir", luceneDirectory);
			@params.Add(typeof(LockManager), lockManager);
			return txModule.registerDataSource(name, className, resourceId, @params, true);
		}

///    
///     <summary> * Returns true if Neo4j is started.
///     *  </summary>
///     * <returns> True if Neo4j started </returns>
///     
		public virtual bool started()
		{
			return started;
		}

///    
///     <summary> * Shut down Neo4j. </summary>
///     
		[MethodImpl(MethodImplOptions.Synchronized)]
		public virtual void shutdown()
		{
			if (started)
			{
				config.getNeoModule().Stop();
				config.getIdGeneratorModule().Stop();
				persistenceSource.Stop();
				config.getPersistenceModule().Stop();
				config.getTxModule().Stop();
				config.getEventModule().Stop();
				config.getNeoModule().destroy();
				config.getIdGeneratorModule().destroy();
				persistenceSource.destroy();
				config.getPersistenceModule().destroy();
				config.getTxModule().destroy();
				config.getEventModule().destroy();
			}
			started = false;
		}

		public virtual IEnumerable <RelationshipType> getRelationshipTypes()
		{
			return config.getNeoModule().getRelationshipTypes();
		}

		public virtual bool transactionRunning()
		{
			try
			{
				return config.getTxModule().getTxManager().getTransaction() != null;
			}
			catch (Exception e)
			{
				throw new TransactionFailureException("Unable to get transaction.", e);
			}
		}

		public virtual TransactionManager getTransactionManager()
		{
			return config.getTxModule().getTxManager();
		}
	}
}